package com.msb.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.msb.mall.product.service.CategoryBrandRelationService;
import com.msb.mall.product.vo.Catalog2VO;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.msb.common.utils.PageUtils;
import com.msb.common.utils.Query;

import com.msb.mall.product.dao.CategoryDao;
import com.msb.mall.product.entity.CategoryEntity;
import com.msb.mall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> queryPageWithTree(Map<String, Object> params) {
        //1.查询所有的商品分类信息
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);

        //2.将商品分类信息拆解为树形结构【父子结构】
        //第一步遍历出所有的大类 parent_id=0
        List<CategoryEntity> collect = categoryEntities.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(categoryEntity -> {
                    //根据大类找到所有的小类 递归的方式实现
                    categoryEntity.setChildrens(getCategoryChildrens(categoryEntity,categoryEntities));
                    return categoryEntity;
                }).sorted((entity1, entity2) -> {
                    return (entity1.getSort() == null ? 0 : entity1.getSort()) -
                            (entity2.getSort() == null ? 0 : entity2.getSort());
                }).collect(Collectors.toList());
        //第二步根据大类找到对应的所有小类
        return collect;
    }

    /**
     * 查找改大类下的所有的小类
     * @param categoryEntity 某个大类
     * @param categoryEntities 所有的类别数据
     * @return
     */
    private List<CategoryEntity> getCategoryChildrens(CategoryEntity categoryEntity
            , List<CategoryEntity> categoryEntities) {
        List<CategoryEntity> collect = categoryEntities.stream().filter(entity -> {
            //根据大类找到他的直属小类
            //注意Long 数据比较 不在-128 - 127 之间的数据是 new Long（）对象
            return entity.getParentCid().equals(categoryEntity.getCatId()); //entity小类的父id等于当前categoryEntity大类的id
        }).map(entity -> {
            //根据这个小类递归找到对应的小类
            entity.setChildrens(getCategoryChildrens(entity, categoryEntities));
            return entity;
        }).sorted((entity1, entity2) -> {
            return (entity1.getSort() == null ? 0 : entity1.getSort()) -
                    (entity2.getSort() == null ? 0 : entity2.getSort());
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 逻辑批量删除操作
     * @param asList
     */
    @Override
    public void removeCategoryByIds(List<Long> asList) {
        //TODO 1.检查类别数据是否在其他业务中使用

        // 批量逻辑删除操作
        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);
        Collections.reverse(parentPath);//反制  [225,22,2] ---> [2,22,225]
        return parentPath.toArray(new Long[parentPath.size()]);
    }

    private List<Long> findParentPath(Long catelogId,List<Long> paths){
        paths.add(catelogId);
        CategoryEntity entity = this.getById(catelogId);
        if(entity.getParentCid() !=0 ){
            findParentPath(entity.getParentCid(),paths);
        }
        return paths;
    }

    /**
     *@CacheEvict 在更新数据的时候同步删除缓存中的数据
     * 更新类别名称
     * @param category
     */
    //@CacheEvict(value = "catagory",key = "'getLevelCategory1'")
    /*@Caching(evict = {
            @CacheEvict(value = "catagory",key = "'getLevelCategory1'")
            ,@CacheEvict(value = "catagory",key = "'getCatalog2JSON'")
    })*/
    @CacheEvict(value = "catagory",allEntries = true) //删除catagory分区下所有的信息
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        //更新原始数据
        this.updateById(category);
        //同步更新级联关系中的数据
        if(!StringUtils.isEmpty(category.getName())){
            categoryBrandRelationService.updateCatelogName(category.getCatId(),category.getName());
        }
    }

    /**
     * 查询出所有的商品大类（一级分类）
     *   在注解中我们可以指定对应的缓存的名称,起到一个分区的作用，一般按照业务来区分
     *   @Cacheable({"catagory","product"}) 代表当前的方法的返回结果是需要缓存的，调用该方法的时候，
     *                                          如果缓存中有数据，那么该方法就不会执行
     *                                          如果缓存中没有数据，那么就执行该方法并且把查询的结果缓存起来
     *        缓存处理：
     *              1.存储在Redis中的缓存数据的key是默认生成的，缓存名称：SimpleKey
     *              2.默认缓存的数据过期时间TTL是-1（永久）
     *              3.缓存的数据，默认使用的是jdk的序列化机制
     *        改进：
     *             1.生成的缓存数据我们需要指定自定义的key
     *             2.指定缓存数据的过期时间
     *             3.把缓存的数据保存为JSON数据
     * @return
     */
    @Cacheable(value = {"catagory"},key = "#root.method.name",sync = true)//sync = true  为true 加本地锁
    @Override
    public List<CategoryEntity> getLevelCategory1() {
        System.out.println("查询了数据库操作");
        long start = System.currentTimeMillis();
        List<CategoryEntity> list = baseMapper.queryLevelCategory1();
        System.out.println("查询消耗的时间:"+(System.currentTimeMillis() - start));
        return list;
    }

    /**
     * 根据父编号获取对应的子菜单信息
     * @param list
     * @param parentCid
     * @return
     */
    private List<CategoryEntity> queryByParentCid(List<CategoryEntity> list,Long parentCid){
        List<CategoryEntity> collect = list.stream().filter(item -> {
            return item.getParentCid().equals(parentCid);
        }).collect(Collectors.toList());
        return collect;
    }

    //本地缓存
    private Map<String,Map<String, List<Catalog2VO>>> cache = new HashMap<>();

    /**
     * 查询所有分类数据，并且完成一级二级三级的关联
     * @return
     */
    @Cacheable(value = "catagory",key = "#root.method.name")
    @Override
    public Map<String, List<Catalog2VO>> getCatalog2JSON(){
        //获取所有的分类数据
        List<CategoryEntity> list = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        // 获取所有的一级分类的数据
        List<CategoryEntity> levelCategory1 = this.queryByParentCid(list, 0L);
        //把一级分类的数据转换为Map容器 key就是一级分类的编号，value就是一级分类对应的二级分类的数据
        Map<String, List<Catalog2VO>> map = levelCategory1.stream().collect(Collectors.toMap(
                key -> (key.getCatId().toString())
                , value -> {
                    //根据一级分类的编号，查询出对应的二级分类的数据
                    List<CategoryEntity> l2Catalogs = this.queryByParentCid(list, value.getCatId());
                    List<Catalog2VO> catalog2VOS = null;
                    if (l2Catalogs != null) {
                        catalog2VOS = l2Catalogs.stream().map(l2 -> {
                            //需要把查询出来的二级分类的数据填充到Catalog2VO中
                            Catalog2VO catalog2VO = new Catalog2VO(
                                    l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName()
                            );
                            //根据二级分类的数据找到三级分类的信息
                            List<CategoryEntity> l3Catalogs = this.queryByParentCid(list, l2.getCatId());
                            if (l3Catalogs != null) {
                                List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catalogs.stream().map(l3 -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(
                                            l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName()
                                    );
                                    return catalog3VO;
                                }).collect(Collectors.toList());
                                //三级分类关联二级分类
                                catalog2VO.setCatalog3List(catalog3VOS);
                            }
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }
                    return catalog2VOS;
                }
        ));
        return map;
    }

    /**Redis缓存查询数据
     * 查询出所有的二级和三级分类的数据并封装为JSON对象
     * @return
     */
    //@Override
    public Map<String, List<Catalog2VO>> getCatalog2JSONRedis(){
        String key = "catalogJSON";
        //从Redis中获取分类的信息
        String catalogJSON = stringRedisTemplate.opsForValue().get(key);
        if(StringUtils.isEmpty(catalogJSON)){
            System.out.println("缓存没有命中");
            // 缓存中没有数据，需要从数据库中查询
            Map<String, List<Catalog2VO>> catalog2JSONForDb = getCatalog2JSONForDbWithRedisson();

            return catalog2JSONForDb;
        }
        System.out.println("缓存命中了");
        //表示缓存命中了数据，从缓存中获取信息，然后返回
        Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON,
                new TypeReference<Map<String, List<Catalog2VO>>>() {
        });
        return stringListMap;
    }

    public Map<String, List<Catalog2VO>> getCatalog2JSONForDbWithRedisson() {
        String keys = "catalogJSON";
        //获取分布式锁对象
        RLock lock = redissonClient.getLock("Catalog2JSON-lock");
        Map<String, List<Catalog2VO>> data = null;
        try {
            //加锁
            lock.lock();
            data = getDataForDB(keys);
        }finally {
            //释放锁
            lock.unlock();
        }
        return data;
    }

    public Map<String, List<Catalog2VO>> getCatalog2JSONForDbWithRedisLock() {
        String keys = "catalogJSON";
        //加锁 在执行插入操作的同时设置了过期时间
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);
        if(lock){
            System.out.println("获取分布式锁成功...");
            Map<String, List<Catalog2VO>> data = null;
            try {
                /*//给对应的key设置过期时间
                stringRedisTemplate.expire("lock",20,TimeUnit.SECONDS);*/
                //加锁成功
                data = getDataForDB(keys);
            }finally {
                //保证获取当前key对应的值 和 根据值删除锁   完成原子性
                String srcipts = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(srcipts,Long.class)
                        ,Arrays.asList("lock"),uuid);
                /*//获取当前key对应的值
                String val = stringRedisTemplate.opsForValue().get("lock");
                if(uuid.equals(val)){
                    //说明这把锁是自己的
                    // 从数据库中获取数据成功后，我们应该要释放锁
                    stringRedisTemplate.delete("lock");
                }*/
            }
            return data;
        }else {
            //加锁失败
            //休眠+重试
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("获取锁失败...");
            return getCatalog2JSONForDbWithRedisLock();
        }
    }

    /**
     * 从数据库查询操作
     * @param keys
     * @return
     */
    private Map<String, List<Catalog2VO>> getDataForDB(String keys) {
        //先去缓存中查询有没有数据，如果有就返回，否则查询数据库
        //从Redis中获取分类的信息
        String catalogJSON = stringRedisTemplate.opsForValue().get(keys);
        if (!StringUtils.isEmpty(catalogJSON)) {
            //表示缓存命中，从缓存中获取信息，然后返回
            Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON,
                    new TypeReference<Map<String, List<Catalog2VO>>>() {
                    });
            return stringListMap;
        }
        System.out.println("------------>查询数据库操作");

        //获取所有的分类数据
        List<CategoryEntity> list = baseMapper.selectList(new QueryWrapper<CategoryEntity>());
        // 获取所有的一级分类的数据
        List<CategoryEntity> levelCategory1 = this.queryByParentCid(list, 0L);
        //把一级分类的数据转换为Map容器 key就是一级分类的编号，value就是一级分类对应的二级分类的数据
        Map<String, List<Catalog2VO>> map = levelCategory1.stream().collect(Collectors.toMap(
                key -> (key.getCatId().toString())
                , value -> {
                    //根据一级分类的编号，查询出对应的二级分类的数据
                    List<CategoryEntity> l2Catalogs = this.queryByParentCid(list, value.getCatId());
                    List<Catalog2VO> catalog2VOS = null;
                    if (l2Catalogs != null) {
                        catalog2VOS = l2Catalogs.stream().map(l2 -> {
                            //需要把查询出来的二级分类的数据填充到Catalog2VO中
                            Catalog2VO catalog2VO = new Catalog2VO(
                                    l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName()
                            );
                            //根据二级分类的数据找到三级分类的信息
                            List<CategoryEntity> l3Catalogs = this.queryByParentCid(list, l2.getCatId());
                            if (l3Catalogs != null) {
                                List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catalogs.stream().map(l3 -> {
                                    Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(
                                            l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName()
                                    );
                                    return catalog3VO;
                                }).collect(Collectors.toList());
                                //三级分类关联二级分类
                                catalog2VO.setCatalog3List(catalog3VOS);
                            }
                            return catalog2VO;
                        }).collect(Collectors.toList());
                    }
                    return catalog2VOS;
                }
        ));
        //从数据库中获取到了对应的信息 然后在缓存中也存储一份信息
        /*cache.put("getCatalog2JSON",map);*/
        if (map == null) {
            //那就说明查询的数据在数据库中不存在   防止缓存穿透
            stringRedisTemplate.opsForValue().set(keys, "1", 5, TimeUnit.SECONDS);
        } else {
            //从数据库中查询到的数据，同时我们也需要在缓存中也存储一份
            String json = JSON.toJSONString(map);
            //防止缓存雪崩
            stringRedisTemplate.opsForValue().set("catalogJSON", json, 10, TimeUnit.MINUTES);
        }
        return map;
    }

    /**从数据库查询结果
     * 查询出所有的二级和三级分类的数据并封装为JSON对象
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalog2JSONForDb() {

        //缓存击穿  加锁
        synchronized (this){
            /*if(cache.containsKey("getCatalog2JSON")){
            //直接从缓存中获取
            return cache.get("getCatalog2JSON");
            }*/

            //先去缓存中查询有没有数据，如果有就返回，否则查询数据库
            String keys = "catalogJSON";
            //从Redis中获取分类的信息
            return getDataForDB(keys);
        }
    }


}